package com.chcit.share.security.token.filter;

import com.alibaba.fastjson.JSONObject;
import com.chcit.share.security.ChcitToken;
import com.chcit.share.security.ChcitUser;
import com.chcit.share.security.SecurityProperties;
import com.chcit.share.security.constant.SecurityConstants;
import com.chcit.share.security.util.SecurityUtils;
import com.chcit.share.security.util.TokenUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.NullRememberMeServices;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.Assert;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;


/**
    启动登录认证流程过滤器
 * 1. 覆写认证方法，修改用户名、密码的获取方式，具体原因看代码注释
 * 2. 覆写认证成功后的操作，移除后台跳转，添加生成令牌并返回给客户端
 * 除了使用上面的登录认证过滤器拦截 /login Post请求之外，我们也可以不使用上面的过滤器，通过自定义登录接口实现，
 * 只要在登录接口手动触发登录流程并生产令牌即可。
 * 其实 Spring Security 的登录认证过程只需 调用 AuthenticationManager 的 authenticate(Authentication authentication) 方法，
 * 最终返回认证成功的 Authentication 实现类并存储到SpringContexHolder 上下文即可，
 * 这样后面授权的时候就可以从 SpringContexHolder 中获取登录认证信息，并根据其中的用户信息和权限信息决定是否进行授权。
 * 注意：如果使用此登录控制器触发登录认证，需要禁用登录认证过滤器，即将 WebSecurityConfig 中的以下配置项注释即可，
 * 否则访问登录接口会被过滤拦截，执行不会再进入此登录接口
 * /开启登录认证流程过滤器，如果使用LoginController的login接口, 需要注释掉此过滤器，根据使用习惯二选一即可
 */
//@Component
public class ChcitTokenGeneratorFilter extends UsernamePasswordAuthenticationFilter {

    @Autowired
    CacheManager cacheManager;
    private SecurityProperties securityProperties;


    private UserDetailsService userDetailService;

    public ChcitTokenGeneratorFilter(AuthenticationManager authManager, SecurityProperties securityProperties,
                                     UserDetailsService userDetailService) {
        setAuthenticationManager(authManager);
        this.securityProperties = securityProperties;
        this.userDetailService = userDetailService;

    }
    
    private RememberMeServices rememberMeServices = new NullRememberMeServices();



    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 可以在此覆写尝试进行登录认证的逻辑，登录成功之后等操作不再此方法内
        // 如果使用此过滤器来触发登录认证流程，注意登录请求数据格式的问题
        // 此过滤器的用户名密码默认从request.getParameter()获取，但是这种
        // 读取方式不能读取到如 application/json 等 post 请求数据，需要把
        // 用户名密码的读取逻辑修改为到流中读取request.getInputStream()

        String body = getBody(request);
        JSONObject jsonObject;
        String username;
        String password ;
        if(StringUtils.isNotBlank(body)){
            jsonObject = JSONObject.parseObject(body);
            username = jsonObject.getString("username");
            password = jsonObject.getString("password");
        }else{
            username = request.getParameter("username");
            password = request.getParameter("password");
        }


        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        ChcitAuthenticatioToken authRequest = new ChcitAuthenticatioToken(userDetailService.loadUserByUsername(username),
        		password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);

    }
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        // 存储登录认证信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authResult);
        // 记住我服务
        getRememberMeServices().loginSuccess(request, response, authResult);
        // 触发事件监听器
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        String token;
        String username = authResult.getName();
        Integer expireTime = securityProperties.getTokenExpireTime();//单位分钟
        if(!securityProperties.isJwtToken()){
        	//普通token
            token = UUID.randomUUID().toString().replace("-", "");
            ChcitToken tokenUser = new ChcitToken(username,System.currentTimeMillis()+expireTime*60*1000);
            ChcitUser chcitUser = SecurityUtils.getUser();
            Assert.notNull(chcitUser,"null chcit user");
            chcitUser.setToken(tokenUser);
            cacheManager.getCache(SecurityConstants.TOKEN_CACHE_NAME).put(token,chcitUser);
//            CacheUtil.setCacheValue(SecurityConstants.TOKEN_CACHE_NAME, token, tokenUser);
        }else{
            if(request.getHeader(SecurityConstants.HEADER_JWT_TIME)!= null){
                expireTime = Integer.parseInt(request.getHeader(SecurityConstants.HEADER_JWT_TIME));
            }else{
                expireTime = securityProperties.getTokenExpireTime();
            }
            token = TokenUtils.generateToken(authResult,expireTime);
        }
        // 生成并返回token给客户端，后续访问携带此token
        Map<String,Object> rst = new HashMap<>();
        rst.put("token", token);
//        ProcessResult<Object> pr = new ProcessResult<Object>(rst);
//        WebUtils.renderJson(response, pr);

    }

    /** 
     * 获取请求Body
     * @param request
     * @return
     */
    public String getBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }


}
